/*
 * Copyright 2023-2024, Haiku, Inc. All rights reserved.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *		Haiku Development Team
 */

#include <KernelExport.h>

#include <arch/elf.h>
#include <boot/elf.h>
#include <elf_private.h>

#ifndef _BOOT_MODE
#include <arch_cpu.h>
#include <boot/kernel_args.h>
#include <kernel.h>
#include <kimage.h>

// #define TRACE_ARCH_ELF
#ifdef TRACE_ARCH_ELF
#define TRACE(x) dprintf x
#else
#define TRACE(x) ;
#endif

status_t
image_relocate(image_t *image)
{
    int32 relocationCount = image->num_needed_relocations;
    elf_rela *relocations = image->needed_relocations;

    TRACE(("arch_elf: relocating image %" B_PRId32 " %s, %" B_PRId32 " relocations\n",
           image->id, image->name, relocationCount));

    // TODO: Implement LoongArch64 specific relocations
    // 参考LoongArch64 ELF ABI规范

    // 处理R_LARCH_RELATIVE类型的重定位
    for (int32 i = 0; i < relocationCount; i++)
    {
        elf_rela *rel = &relocations[i];

        TRACE(("arch_elf: reloc[%" B_PRId32 "]: type %d, offset 0x%" B_PRIx64 ", sym 0x%lx, "
               "addend 0x%" B_PRIx64 "\n",
               i, ELF64_R_TYPE(rel->r_info),
               rel->r_offset, ELF64_R_SYM(rel->r_info), rel->r_addend));

        switch (ELF64_R_TYPE(rel->r_info))
        {
        case R_LARCH_RELATIVE:
        {
            // 处理相对地址重定位
            addr_t *target = (addr_t *)(image->regions[0].delta + rel->r_offset);
            *target = image->regions[0].delta + rel->r_addend;
            break;
        }

        case R_LARCH_JUMP_SLOT:
        {
            // 处理PLT跳转表重定位
            addr_t symbolAddress = image->regions[0].delta + rel->r_addend;
            addr_t *target = (addr_t *)(image->regions[0].delta + rel->r_offset);
            *target = symbolAddress;
            break;
        }

        case R_LARCH_64:
        {
            // 处理64位绝对地址重定位
            addr_t symbolAddress = image->regions[0].delta + rel->r_addend;
            addr_t *target = (addr_t *)(image->regions[0].delta + rel->r_offset);
            *target = symbolAddress;
            break;
        }

        case R_LARCH_NONE:
            // 无操作重定位
            break;

        default:
            TRACE(("arch_elf: unhandled relocation type %" B_PRId32 "\n",
                   ELF64_R_TYPE(rel->r_info)));
            return B_ERROR;
        }
    }

    return B_OK;
}

status_t
arch_elf_relocate_rela(preloaded_elf64_image *image, elf64_rela *rel, int relLength)
{
    TRACE(("arch_elf_relocate_rel: entry\n"));

    for (int i = 0; i < relLength / (int)sizeof(elf64_rela); i++)
    {
        switch (ELF64_R_TYPE(rel[i].r_info))
        {
        case R_LARCH_NONE:
            break;

        case R_LARCH_RELATIVE:
        {
            addr_t *ptr = (addr_t *)(image->text_region.delta + rel[i].r_offset);
            *ptr = image->text_region.delta + rel[i].r_addend;
            break;
        }

        default:
            dprintf("arch_elf_relocate_rela: unhandled relocation type %ld\n",
                    ELF64_R_TYPE(rel[i].r_info));
            return B_ERROR;
        }
    }

    return B_OK;
}

#endif // !_BOOT_MODE

status_t
elf_verify_program_header(const elf64_ehdr *eheader, const elf64_phdr *pheader)
{
    switch (pheader->p_type)
    {
    case PT_DYNAMIC:
    case PT_INTERP:
    case PT_PHDR:
    case PT_LOAD:
    case PT_NOTE:
    case PT_GNU_STACK:
    case PT_GNU_RELRO:
    case PT_GNU_EH_FRAME:
        return B_OK;
    }

    return B_BAD_DATA;
}

status_t
elf_verify_header(const elf64_ehdr *header)
{
    if (memcmp(header->e_ident, ELFMAG, 4) != 0)
        return B_NOT_AN_EXECUTABLE;

    if (header->e_ident[EI_CLASS] != ELFCLASS64)
        return B_NOT_AN_EXECUTABLE;

    if (header->e_ident[EI_DATA] != ELFDATA2LSB)
        return B_NOT_AN_EXECUTABLE;

    if (header->e_ident[EI_VERSION] != EV_CURRENT)
        return B_NOT_AN_EXECUTABLE;

    if (header->e_ident[EI_OSABI] != ELFOSABI_SYSV && header->e_ident[EI_OSABI] != ELFOSABI_LINUX)
        return B_NOT_AN_EXECUTABLE;

    if (header->e_type != ET_EXEC && header->e_type != ET_DYN)
        return B_NOT_AN_EXECUTABLE;

    if (header->e_machine != EM_LOONGARCH)
        return B_NOT_AN_EXECUTABLE;

    if (header->e_version != EV_CURRENT)
        return B_NOT_AN_EXECUTABLE;

    return B_OK;
}